package com.xiyuan.smartutils.template.engine.expression.operator;

import com.xiyuan.smartutils.Classes;
import com.xiyuan.smartutils.Types;
import com.xiyuan.smartutils.template.engine.ExpressionParser;
import com.xiyuan.smartutils.template.engine.VariableMap;
import com.xiyuan.smartutils.template.engine.exception.ExpressionException;
import com.xiyuan.smartutils.template.engine.expression.Expression;
import com.xiyuan.smartutils.template.engine.expression.symbol.Symbol;
import com.xiyuan.smartutils.template.engine.statement._FUNCTION;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

/**
 * 方法操作符，或obj.method(param1, param2)
 *
 * @author lgz 2020年12月1日 新建与整理
 */
public class _Method extends Operator {
    private static final long serialVersionUID = 1L;
    
    // 是否被使用new的方式去调用
    private boolean isNew;
    private final String methodName;
    private final ArrayList<Expression> paramList;
    
    // 预编译括号表达式
    private _Parenthesis paramExpression;
    private boolean isParsed;
    
    public _Method(String path, int line, int column, String methodName) {
        super(path, line, column);
        this.methodName = methodName;
        this.paramList = new ArrayList<Expression>(0);
    }
    
    @Override
    public int getType() {
        return METHOD;
    }
    
    @Override
    protected Object build(VariableMap variableMap)  {
        
        if (isNew) {
            // 创建函数
            String claz = target != null ? target.toString() : null;
            if (claz == null) {claz = methodName;}
            else {claz = claz + "." + methodName;}
            
            return invokeNewInstance(variableMap, claz);
        }
        
        // 调用函数
        if (target == null) {
            // 如果前置对象不存在，则认为是调用全局函数
            
            Object function = variableMap.get(_FUNCTION.getFunctionKey(methodName));
            if (!(function instanceof _FUNCTION)) {
                throw new ExpressionException(this, "方法表达式{" + this + "}页面未找到该函数的定义");
            }
            
            return invokeFunction(variableMap);
        }
        else {// 对象存在则认为是调用对象的方法
            return invokeMethod(variableMap);
        }
    }
    
    /**
     * 获取函数
     *
     */
    private Object invokeFunction(VariableMap variableMap)  {
        Object function = variableMap.get(_FUNCTION.getFunctionKey(methodName));
        if (!(function instanceof _FUNCTION)) {
            throw new ExpressionException(this, "方法表达式{" + this + "}页面未找到该函数的定义");
        }
        
        _FUNCTION func = (_FUNCTION) function;
        List<Object> paramArray = new ArrayList<Object>(paramList.size());
        for (int i = 0; i < paramList.size(); i++) {paramArray.add(paramList.get(i).execute(variableMap));}
        
        return func.call(variableMap, paramArray);
    }
    
    //
    
    /**
     * 方法是构造函数
     *
     */
    private Object invokeNewInstance(VariableMap variableMap, String className)  {
        
        Class<?> newClass = VariableMap.getJavaClass(className);
        if (newClass == null) {newClass = VariableMap.getSystemClass(className);}
        
        // if (newClass == null)
        //     newClass = Global.forName(className);
        
        if (newClass == null) {
            newClass = Classes.forName(className);// 尝试加载
        }
        
        if (newClass == null) {
            throw new ExpressionException(this, "方法表达式{" + this + "}对应的new构造函数类名不正确");
        }
        
        Object[] paramArray = new Object[paramList.size()];
        for (int i = 0; i < paramList.size(); i++) {paramArray[i] = paramList.get(i).execute(variableMap);}
        
        // 找到一个构造函数匹配上的
        Constructor<?> constructor = null;
        Constructor<?>[] constructors = newClass.getConstructors();
        for (Constructor<?> c : constructors) {
            // 判断参数个数
            Class<?>[] paramTypes = c.getParameterTypes();
            if (paramTypes.length != paramArray.length) {continue;}
            
            // 判断每个参数类型
            boolean isSuccess = true;
            for (int i = 0; i < paramTypes.length; i++) {
                Class<?> clazz = paramTypes[i];
                Object param = paramArray[i];
                if (param == null) {// 参数为null时，要求不是基本类型
                    if (Types.isPrimitive(clazz)) {
                        isSuccess = false;
                        break;
                    }
                }
                else if (Types.isPrimitive(clazz)) {// 基本类型，数值型不一定匹配，要适配
                    if ((Types.isInteger(clazz) && !Types.isInteger(param))// long,int,short,byte
                            || (Types.isDecimal(clazz) && !Types.isDecimal(param))// float,double
                            || (Types.isBoolean(clazz) && !Types.isBoolean(param))// boolean
                            || (Types.isChar(clazz) && !Types.isChar(param)))// char
                    {
                        isSuccess = false;
                        break;
                    }
                }
                else {// 对象类型，判断是否其子类或本类
                    if (clazz != param.getClass() && !clazz.isAssignableFrom(param.getClass())) {
                        isSuccess = false;
                        break;
                    }
                }
            }
            
            if (!isSuccess) {continue;}
            
            constructor = c;
            break;
        }
        
        if (constructor == null) {
            throw new ExpressionException(this, "方法表达式{" + this + "}，格式不正确，没有找到对应的构造函数");
        }
        
        // _Integer,_Double可能格式不同，要匹配
        Class<?>[] paramTypes = constructor.getParameterTypes();
        for (int i = 0; i < paramTypes.length; i++) {
            Class<?> clazz = paramTypes[i];
            if (!Types.isNumber(clazz)) {continue;}
            
            if (Types.isDecimal(clazz)) {// 小数
                Object param = paramArray[i];
                if (!Types.isDecimal(param)) {
                    throw new ExpressionException(this, "方法表达式{" + this + "}，格式不正确，参数不匹配，要求小数");
                }
                
                // 需要对double/float进行匹配
                if (Types.isDouble(clazz)) {paramArray[i] = ((Number) param).doubleValue();}
                else {paramArray[i] = ((Double) param).floatValue();}
            }
            else {// 整数
                Object param = paramArray[i];
                if (!Types.isInteger(param)) {
                    throw new ExpressionException(this, "方法表达式{" + this + "}，格式不正确，参数不匹配，要求整数");
                }
                
                // 需要对long/byte/short/int进行匹配
                if (Types.isLong(clazz)) {paramArray[i] = ((Number) param).longValue();}
                else if (Types.isInt(clazz)) {paramArray[i] = ((Number) param).intValue();}
                else if (Types.isShort(clazz)) {paramArray[i] = ((Number) param).shortValue();}
                else {paramArray[i] = ((Number) param).byteValue();}
            }
        }
        
        // 调用方法
        try {
            constructor.setAccessible(true);
            return constructor.newInstance(paramArray);
        }
        catch (Exception e) {
            if (e instanceof InvocationTargetException) {
                throw new ExpressionException(this,
                                              "方法表达式{" + this + "}，调用失败{" + ((InvocationTargetException) e).getTargetException()
                                                      .getMessage() + "}");
            }
            else {throw new ExpressionException(this, "方法表达式{" + this + "}，调用失败{" + e.getMessage() + "}");}
        }
    }
    
    /**
     * 对象方法
     *
     */
    private Object invokeMethod(VariableMap variableMap) {
        Object obj = target.execute(variableMap);
        if (obj == null) {throw new ExpressionException(this, "方法表达式{" + target + "}未找到方法：" + methodName);}
        
        if (obj != null && !(obj instanceof Class<?>)) {return buildMethod(variableMap, obj.getClass(), obj);}
        
        return buildMethod(variableMap, (Class<?>) obj, null);
    }
    
    public void setNew(boolean isNew) {
        this.isNew = isNew;
    }
    
    /**
     * 对象方法
     *
     */
    private Object buildMethod(VariableMap variableMap, Class<?> targetClass, Object target) {
        // 1.先漏选方法，把名称不同的和要求静态方法但不是静态方法的去掉
        List<Method> normalMethodList = new ArrayList<>();
        List<Method> arrayMethodList = new ArrayList<>();
        
        Method[] methods = targetClass.getMethods();
        for (int i = 0; i < methods.length; i++) {
            Method m = methods[i];
            if (!m.getName().equals(methodName)) {continue;}
            
            if (target == null && !Classes.isStaticMethod(m)) {continue;}
            
            if (Classes.isMethodLastParamArray(m)) {arrayMethodList.add(m);}
            else {normalMethodList.add(m);}
        }
        
        // 未找到对应的方法
        if (normalMethodList.size() + arrayMethodList.size() == 0) {
            throw new ExpressionException(this,
                                          "方法表达式{" + this + "}，格式不正确，没有找到对应的方法名或要求是静态方法");
        }
        
        // 生成方法参数
        Object[] paramArray = new Object[paramList.size()];
        for (int i = 0; i < paramList.size(); i++) {paramArray[i] = paramList.get(i).execute(variableMap);}
        
        // 2.通过参数匹配，找到对应的方法
        Method method = null;
        
        // 2.1优先找最后一个参数不是数组的方法
        for (Method m : normalMethodList) {
            Class<?>[] paramTypes = m.getParameterTypes();
            if (Classes.isMethodParamMatch(paramTypes, paramArray)) {// 正常的参数个数匹配成功
                method = m;
                break;
            }
        }
        
        if (method == null) {
            // 2.2再找最后一个参数是数组的，转化个数相等再比较
            for (Method m : arrayMethodList) {
                // 判断参数个数
                Class<?>[] paramTypes = m.getParameterTypes();
                int typeLen = paramTypes.length;
                int arrayLen = paramArray.length;
                
                if (typeLen > arrayLen) {
                    // 大于的
                    continue;
                }
                else if (typeLen == arrayLen) {
                    // 相等的
                    if (Classes.isMethodParamMatch(paramTypes, paramArray)) {
                        method = m;
                        break;
                    }
                }
                else {// 小于的，则支持参数最后部分转化为多个参数方式String... params的形参，而实参是"abc", "bde"的情况
                    Class<?> paramLastType = paramTypes[typeLen - 1].getComponentType();
                    Class<?>[] newParamTypes = new Class<?>[arrayLen];
                    for (int i = 0; i < typeLen - 1; i++) {
                        newParamTypes[i] = paramTypes[i];
                    }
                    
                    for (int i = typeLen - 1; i < arrayLen; i++) {
                        newParamTypes[i] = paramLastType;
                    }
                    
                    if (Classes.isMethodParamMatch(newParamTypes, paramArray)) {
                        method = m;
                        break;
                    }
                }
            }
        }
        
        // 2.3 方法参数没找到F
        if (method == null) {
            throw new ExpressionException(this, "方法表达式{" + this + "}，格式不正确，方法名找到，但参数个数未对应");
        }
        
        // 3._Integer,_Double可能格式不同，要匹配
        Class<?>[] paramTypes = method.getParameterTypes();
        for (int i = 0; i < paramTypes.length; i++) {
            Class<?> clazz = paramTypes[i];
            if (!Types.isNumber(clazz)) {continue;}
            
            if (Types.isDecimal(clazz)) {// 小数
                Object param = paramArray[i];
                if (!Types.isDecimal(param)) {
                    throw new ExpressionException(this,
                                                  "方法表达式{" + this + "}，格式不正确，方法名找到且参数个数一致，参数值不匹配，要求小数");
                }
                
                // 需要对double/float进行匹配
                if (Types.isDouble(clazz)) {paramArray[i] = ((Number) param).doubleValue();}
                else {paramArray[i] = ((Double) param).floatValue();}
            }
            else {// 整数
                Object param = paramArray[i];
                if (!Types.isInteger(param)) {
                    throw new ExpressionException(this,
                                                  "方法表达式{" + this + "}，格式不正确，方法名找到且参数个数一致，参数不匹配，要求整数");
                }
                
                // 需要对long/byte/short/int进行匹配
                if (Types.isLong(clazz)) {paramArray[i] = ((Number) param).longValue();}
                else if (Types.isInt(clazz)) {paramArray[i] = ((Number) param).intValue();}
                else if (Types.isShort(clazz)) {paramArray[i] = ((Number) param).shortValue();}
                else {paramArray[i] = ((Number) param).byteValue();}
            }
        }
        
        // 4.最后一个形参是数组，而实参不是数组的转变为数组
        boolean lastParamIsArray = paramTypes.length > 0 && Types.isArray(paramTypes[paramTypes.length - 1]);
        if (lastParamIsArray && !Types.isArray(paramArray[paramTypes.length - 1])) {
            Class<?> paramLastType = paramTypes[paramTypes.length - 1].getComponentType();
            int num = paramArray.length - paramTypes.length + 1;
            Object[] objs = Classes.newInstance(paramLastType, num);
            for (int i = 0; i < num; i++) {
                objs[i] = paramArray[paramTypes.length - 1 + i];
            }
            
            Object[] newParamArray = new Object[paramTypes.length];
            for (int i = 0; i < newParamArray.length - 1; i++) {
                newParamArray[i] = paramArray[i];
            }
            newParamArray[newParamArray.length - 1] = objs;
            paramArray = newParamArray;
        }
        
        // 5.调用方法
        try {
            method.setAccessible(true);
            if (Classes.isStaticMethod(method)) {return method.invoke(null, paramArray);}
            else {return method.invoke(target, paramArray);}
        }
        catch (Exception e) {
            if (e instanceof InvocationTargetException) {
                throw new ExpressionException(this,
                                              "方法表达式{" + this + "}，调用失败{" + ((InvocationTargetException) e).getTargetException()
                                                      .getMessage() + "}");
            }
            else {throw new ExpressionException(this, "方法表达式{" + this + "}，调用失败{" + e.getMessage() + "}");}
        }
    }
    
    @Override
    public String toString() {
        StringBuilder strb = new StringBuilder();
        if (isNew) {strb.append("new ");}
        
        if (target != null) {
            strb.append(target);
            strb.append(".");
        }
        
        strb.append(methodName);
        if (isParsed) {
            strb.append("(");
            if (!paramList.isEmpty()) {
                strb.append(paramList.get(0));
                for (int i = 1; i < paramList.size(); i++) {
                    strb.append(", ").append(paramList.get(i));
                }
            }
            strb.append(")");
        }
        else {
            strb.append(paramExpression == null ? "()" : paramExpression);
        }
        return strb.toString();
    }
    
    
    public String getMethodName() {
        return methodName;
    }
    
    /**
     * 合并前面的对象表达式
     *
     * @return 设置目标
     */
    @Override
    public _Method setTarget(Expression target) {
        super.setTarget(target);
        return this;
    }
    
    public _Method setParamExpression(_Parenthesis bracket) {
        this.paramExpression = bracket;
        return this;
    }
    
    /**
     * 解析括号得到实参列表
     */
    @Override
    public void parse() throws ExpressionException {
        if (paramExpression == null) {throw new ExpressionException(this, "函数表达式缺少（）表达式");}
        
        ArrayList<Expression> eList = paramExpression.getExpressionList();
        // 解析所有操作符
        ExpressionParser.parse_operators(eList);
        // 对结果进行分析，除逗号外都是参数 和逗号之间间隔
        int limit = eList.size() - 1;
        for (int i = 0; i <= limit; i++) {
            Expression curr = eList.get(i);
            if (i == limit || curr.getType() == Symbol.SYMBOL_COMMA) {
                if (curr.getType() == Symbol.SYMBOL_COMMA) {
                    eList.remove(i);
                    limit--;
                    i--;
                    continue;
                }
                
                paramList.add(curr);
                continue;
            }
            
            paramList.add(curr);
            Expression next = eList.get(i + 1);
            if (next.getType() != Symbol.SYMBOL_COMMA) {
                throw new ExpressionException(curr, "函数表达式中参数{" + curr + "}和{" + next + "}之间缺少‘,’");
            }
        }
        
        eList.trimToSize();
        eList = null;
        isParsed = true;
        paramExpression = null;
        paramList.trimToSize();
    }
}
